package com.kehutong.payfees.admin;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

import com.kehutong.common.util.Token;
import com.kehutong.payfees.app.AppChargeAction;
import com.kehutong.payfees.entity.*;
import com.kehutong.payfees.entity.receipt.ReceiptOrder;
import com.kehutong.payfees.enums.*;
import com.kehutong.payfees.service.receipt.PayManageServiceImpl;
import com.kehutong.payfees.service.receipt.ReceiptOrderServiceImpl;
import com.kehutong.payfees.util.BasicClient;
import com.kehutong.payfees.util.ExcelUtil;
import com.kehutong.payfees.util.JDBCUtil;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.coraframework.authz.HasPermission;
import org.coraframework.converter.Converter;
import org.coraframework.converter.util.StringUtil;
import org.coraframework.inject.Inject;
import org.coraframework.json.JSONArray;
import org.coraframework.json.JSONObject;
import org.coraframework.logger.Logger;
import org.coraframework.logger.LoggerFactory;
import org.coraframework.mvc.MvcAction;
import org.coraframework.mvc.http.Bean;
import org.coraframework.mvc.http.ReqMapping;
import org.coraframework.mvc.http.result.Result;
import org.coraframework.orm.Pool;
import org.coraframework.orm.jdbc.JdbcSession;
import org.coraframework.orm.jdbc.Page;
import org.coraframework.orm.jdbc.execute.entity.FindArray;
import org.coraframework.orm.jdbc.execute.entity.FindPage;
import org.coraframework.orm.util.Enums;
import org.coraframework.util.Objects;

import com.kehutong.common.ExcelHeaders;
import com.kehutong.common.RemarkBuilder;
import com.kehutong.common.math.CalculatorUtils;
import com.kehutong.common.urlconfig.FileUrlConfig;
import com.kehutong.payfees.service.ChargeBillAnnexServiceImpl;
import com.kehutong.payfees.service.ChargeBillServiceImpl;
import com.kehutong.payfees.service.ChargeCallServiceImpl;
import com.kehutong.payfees.service.ChargeLogServiceImpl;

/**
 * 缴费账单
 */
@MvcAction
@ReqMapping("/admin/charge/bill")
public class ChargeBillAction {

    private static final Logger logger = LoggerFactory.getLogger(ChargeBillAction.class);

    @Inject
    private ChargeBillServiceImpl chargeBillService;
    @Inject
    private ChargeBillAnnexServiceImpl chargeBillAnnexService;
    @Inject
    private ChargeCallServiceImpl chargeCallService;
    @Inject
    private FileUrlConfig fileUrlConfig;
    @Inject
    private ChargeLogServiceImpl chargeLogService;
    @Inject
    private JdbcSession jdbcSession;
    @Inject
    private BasicClient basicClient;
    @Inject
    private ReceiptOrderServiceImpl receiptOrderServiceImpl;
    @Inject
    private PayManageServiceImpl payManageService;

    /**
     * 销单
     * @return
     */
    @ReqMapping("/change")
    public Object closeBill(String id) {
        ChargeBill bill = Pool.get(ChargeBill.class, id);
        if(Objects.isNull(bill) || bill.isDeleted()){
            return Result.error(214001, "该账单不存在或已被删除!");
        }
        if(bill.getPaidState().equals(BillPaidState.PAID)){
            return Result.error(214002, "该账单已支付!");
        }
        bill.setPaidState(BillPaidState.PAID);
        jdbcSession.updateById(bill);

        return Result.success();
    }

    @HasPermission("charge:bill:view")
    @ReqMapping("/page")
    public Object page(JSONObject req) {
        FindPage<ChargeBill> find = jdbcSession.findPage(ChargeBill.class)
                .eq(req, "enableState", "paidState")
                .like(req, "communityName", "buildingName")
                .eq("deleted", false)
                .order("communityNo", "DESC")
                .order("buildingName", "ASC")
                .order("unitName", "ASC")
                .order("floorName", "ASC")
                .order("houseName", "ASC")
                .order("billDateFrom", "ASC")
                .order("shouldPayDate", "ASC")
                .order("id", "ASC")
                .page(req);

        JDBCUtil.between(req, find, "createTime");

        if (StringUtil.isNotBlank(req.getString("houseName"))) {
            find.markBegin()
                    .like("unitName", req.get("houseName"))
                    .or()
                    .like("floorName", req.get("houseName"))
                    .or()
                    .like("houseName", req.get("houseName"))
                    .markEnd();
        }

        // 应付日期 包含在查询范围内
        if (StringUtil.isNotBlank(req.getString("shouldPayDateFrom"))) {
            find.maxEq("shouldPayDate", req.get("shouldPayDateFrom"));
        }
        if (StringUtil.isNotBlank(req.getString("shouldPayDateTo"))) {
            find.minEq("shouldPayDate", req.get("shouldPayDateTo"));
        }
        // 账单周期 查出与查询条件范围有交集的
        if (StringUtil.isNotBlank(req.getString("billDateTo"))) {
            find.minEq("billDateFrom", req.get("billDateTo"));
        }
        if (StringUtil.isNotBlank(req.getString("billDateFrom"))) {
            find.maxEq("billDateTo", req.get("billDateFrom"));
        }

        Page<ChargeBill> page = find.exe();

        return Result.success(page);
    }

    @HasPermission("charge:bill:edit")
    @ReqMapping("/update")
    public Object update(Token token, JSONObject req) {
        JSONArray items = req.getJSONArray("items");

        BigDecimal total = BigDecimal.ZERO;
        for (int i = 0; i < items.size(); i++) {
            JSONObject item = items.getJSONObject(i);
            String feeName = item.getString("name");
            if(Objects.nonEmpty(item.getString("lastReadNumber")) && isBigDecimal(item.getString("lastReadNumber"))){
                return Result.error(0, feeName + "请确认上次读数 是有效的数字");
            }
            BigDecimal lastReadNumber = item.getBigDecimal("lastReadNumber");
            if (Objects.nonNull(lastReadNumber) && CalculatorUtils.getDecimalPlace(lastReadNumber) > 1) {
                return Result.error(0, feeName + "请确认上次读数 小数位最多支持1位");
            }
            if (Objects.nonNull(lastReadNumber) && lastReadNumber.compareTo(BigDecimal.ZERO) < 0) {
                return Result.error(0, feeName + "请确认上次读数 不可为负数");
            }

            if(Objects.nonEmpty(item.getString("theReadNumber")) && isBigDecimal(item.getString("theReadNumber"))){
                return Result.error(0, feeName + "请确认本次读数 是有效的数字");
            }
            BigDecimal theReadNumber = item.getBigDecimal("theReadNumber");
            if (Objects.nonNull(theReadNumber) && CalculatorUtils.getDecimalPlace(theReadNumber) > 1) {
                return Result.error(0, feeName + "请确认本次读数 小数位最多支持1位");
            }
            if (Objects.nonNull(theReadNumber) && theReadNumber.compareTo(BigDecimal.ZERO) < 0) {
                return Result.error(0, feeName + "请确认本次读数 不可为负数");
            }

            if(Objects.nonEmpty(item.getString("amount")) && isBigDecimal(item.getString("amount"))){
                return Result.error(0, feeName + "请确认数量 是有效的数字");
            }
            BigDecimal amount = item.getBigDecimal("amount");
            if (Objects.nonNull(amount) && CalculatorUtils.getDecimalPlace(amount) > 2) {
                return Result.error(0, feeName + "请确认数量 小数位最多支持2位");
            }
            if (Objects.nonNull(amount) && amount.compareTo(BigDecimal.ZERO) < 0) {
                return Result.error(0, feeName + "请确认数量 不可为负数");
            }
            if(Objects.nonEmpty(item.getString("unitPrice")) && isBigDecimal(item.getString("unitPrice"))){
                return Result.error(0, feeName + "请确认单价 是有效的数字");
            }
            BigDecimal unitPrice = item.getBigDecimal("unitPrice");
            if (Objects.nonNull(unitPrice) && CalculatorUtils.getDecimalPlace(unitPrice) > 2) {
                return Result.error(0, feeName + "请确认单价 小数位最多支持2位");
            }
            if (Objects.nonNull(unitPrice) && unitPrice.compareTo(BigDecimal.ZERO) < 0) {
                return Result.error(0, feeName + "请确认单价 不可为负数");
            }

            if(Objects.nonEmpty(item.getString("subTotal")) && isBigDecimal(item.getString("subTotal"))){
                return Result.error(0, feeName + "请确认金额 是有效的数字");
            }
            BigDecimal subTotal = item.getBigDecimal("subTotal");
            if (subTotal == null) {
                return Result.error(0, "收费金额不可为空");
            }
            total = total.add(subTotal);
        }
        if (total.compareTo(BigDecimal.ZERO) < 0) {
            return Result.error(0, "账单总金额不可为负数");
        }


        ChargeBill bill = jdbcSession.findOne(ChargeBill.class)
                .eq(req, "id")
                .exe();

        if (bill == null) {
            return Result.error(0, "对象不存在");
        }

        if (bill.getPaidState() != BillPaidState.UNPAID) {
            return Result.error(0, "仅待缴费状态才可修改");
        }

        RemarkBuilder rb = new RemarkBuilder();

        LocalDate billDateFrom = Converter.toLocalDate(req.getString("billDateFrom"));
        rb.append("账单周期（起）", bill.getBillDateFrom(), billDateFrom);
        bill.setBillDateFrom(billDateFrom);

        LocalDate billDateTo = Converter.toLocalDate(req.getString("billDateTo"));
        rb.append("账单周期（止）", bill.getBillDateTo(), billDateTo);
        bill.setBillDateTo(billDateTo);

        LocalDate shouldPayDate = Converter.toLocalDate(req.getString("shouldPayDate"));
        rb.append("应付款日", bill.getShouldPayDate(), shouldPayDate);
        bill.setShouldPayDate(shouldPayDate);


        String oldItemsStr = getItemsStrByBillNo(bill.getId());
        // 删除旧收费项
        deleteBillItemByBillNo(bill.getId());
        for (int i = 0; i < items.size(); i++) {
            JSONObject item = items.getJSONObject(i);
            // 插入新收费项
            insertBillItem(bill.getId(), item);
        }
        bill.setTotal(total);
        jdbcSession.updateById(bill);

        // 记录收费项
        String newItemsStr = getItemsStrByBillNo(bill.getId());
        rb.append("收费项", oldItemsStr, newItemsStr);

        chargeLogService.saveLog(bill.getId(), BillOperateType.UPDATE_INFO, rb.build(), req, token.getUsername(), token.getUuid());

        return Result.success();
    }

    private String getItemsStrByBillNo(String billId) {
        List<ChargeBillItem> oldItems = jdbcSession.findArray(ChargeBillItem.class)
                .eq("billNo", billId)
                .exe();

        StringBuilder sb = new StringBuilder("[");
        for (ChargeBillItem oldItem : oldItems) {
            sb.append(oldItem.getName())
                    .append(":")
                    .append(CalculatorUtils.formatMoneyString(oldItem.getSubTotal()))
                    .append("元；");
        }
        sb.append("]");
        return sb.toString();
    }

    private void deleteBillItemByBillNo(String billNo) {
        List<ChargeBillItem> list = jdbcSession.findArray(ChargeBillItem.class)
                .eq("billNo", billNo)
                .exe();

        jdbcSession.deleteById(list);
    }

    private void insertBillItem(String billId, JSONObject item) {
        ChargeBillItem billItem = Pool.newInstance(ChargeBillItem.class);
        billItem.setBillNo(billId);
        billItem.setName(item.getString("name"));
        billItem.setSubTotal(item.getBigDecimal("subTotal"));
        billItem.setUnitPrice(item.getBigDecimal("unitPrice"));
        billItem.setLastReadNumber(item.getBigDecimal("lastReadNumber"));
        billItem.setTheReadNumber(item.getBigDecimal("theReadNumber"));
        billItem.setAmount(item.getBigDecimal("amount"));
        billItem.setFeeId(item.getString("feeId"));
        jdbcSession.insert(billItem);
    }

    @ReqMapping("/get")
    public Object get(String id) {
        ChargeBill bill = Pool.get(ChargeBill.class, id);
        if (bill == null) {
            return Result.error(0, "对象不存在");
        }

        List<ChargeBillItem> items = jdbcSession.findArray(ChargeBillItem.class)
                .eq("billNo", bill.getId())
                .exe();
        bill.setBillItems(items);

        List<ChargeBillAnnex> annexes = jdbcSession.findArray(ChargeBillAnnex.class)
                .eq("billNo", bill.getId())
                .exe();
        bill.setBillAnnexes(annexes);

        return Result.success(bill);
    }

    /**
     * 逻辑删除
     */
    @HasPermission("charge:bill:edit")
    @ReqMapping("/delete")
    public Object delete(Token token, String id) {
        ChargeBill bill = Pool.get(ChargeBill.class, id);

        if (bill.getPaidState() == BillPaidState.PAID) {
            return Result.error(0, "已缴费状态记录不可删除");
        }

        // handle del
        if (bill.isDeleted()) {
            return Result.success();
        }
        bill.setDeleted(true);
        jdbcSession.updateById(bill);

        chargeLogService.saveLog(bill.getId(), BillOperateType.DELETE, null, token.getUsername(), token.getUuid());

        return Result.success();
    }

    /**
     * 批量逻辑删除
     */
    @HasPermission("charge:bill:edit")
    @ReqMapping("/batch/delete")
    public Object batchDelete(Token token, JSONObject req) {

        List<ChargeBill> bills = jdbcSession.findArray(ChargeBill.class)
                .in("id", req.getJSONArray("ids"))
                .exe();

        for (ChargeBill bill : bills) {
            if (bill.getPaidState() == BillPaidState.PAID) {
                continue;
            }
            bill.setDeleted(true);
            jdbcSession.updateById(bill);

            chargeLogService.saveLog(bill.getId(), BillOperateType.DELETE, req, token.getUsername(), token.getUuid());
        }
        return Result.success();
    }

    @HasPermission("charge:bill:edit")
    @ReqMapping("/readFile")
    public Object readFile(Token token, JSONObject req) throws Exception {
        String[] headerArr = ExcelHeaders.CHARGE_IMPORT_BILL_EXCEL_HEADER;
        List<String> errMsgs = new ArrayList<>();

        InputStream is = fileUrlConfig.download(req.getString("id"));
        Workbook wb = WorkbookFactory.create(is);
        Sheet sheet = wb.getSheetAt(0);

        // 第一行说明 不读
        // 第二行表头
        Row firstRow = sheet.getRow(sheet.getFirstRowNum() + 1);
        List<String> billitems = new ArrayList<>();
        
        int beginTimeIndex = -1, endTimeIndex = -1, payTimeIndex = -1;
        for (int i = firstRow.getFirstCellNum(); i < firstRow.getLastCellNum(); i++) {
            firstRow.getCell(i).setCellType(CellType.STRING);
            String s = firstRow.getCell(i).getStringCellValue();
            if (i < headerArr.length) {
                if (!headerArr[i].equals(s)) {
                    errMsgs.add("请确认" + headerArr[i] + "为第" + i + 1 + "列");
                }
                if ("账单周期（起）".equals(headerArr[i])) {
                    beginTimeIndex = i;
                } else if ("账单周期（止）".equals(headerArr[i])) {
                    endTimeIndex = i;
                } else if ("应付款日".equals(headerArr[i])) {
                    payTimeIndex = i;
                }
            }else{
                billitems.add(s);
            }
        }


        // 表头校验结果
        if (!errMsgs.isEmpty()) {
            return Result.error(0, StringUtil.join(errMsgs, ";"));
        }

        // 第三行开始遍历获取数据
        Map<String, BigDecimal> totalMap = new HashMap<>();
        Map<String, List<JSONObject>> billItemMap = new HashMap<>();
        Map<String, JSONObject> billMap = new HashMap<>();

        List<ChargeFeeType> feeTypes = jdbcSession.findArray(ChargeFeeType.class)
                .exe();
        Map<String, ChargeFeeType> feeTypeMap = feeTypes.stream().collect(Collectors.toMap(ChargeFeeType::getId, Function.identity()));

        for (int rIndex = sheet.getFirstRowNum() + 2; rIndex <= sheet.getLastRowNum(); rIndex++) {
            Row row = sheet.getRow(rIndex);
            Cell cell = row.getCell(0);
            if (Objects.isNull(cell)) {
                continue;
            }

            String houseNo = row.getCell(0).getStringCellValue();
            if (StringUtil.isBlank(houseNo)) {
                errMsgs.add("第" + rIndex + "行：房屋id不能为空");
            }
            LocalDate billDateFrom = ExcelUtil.getLocalDateFromCell(row, beginTimeIndex);
            if (billDateFrom == null) {
                errMsgs.add("第" + rIndex + "行：请确认 账单周期（起） 非空，单元格设置为日期格式");
            }
            LocalDate billDateTo = ExcelUtil.getLocalDateFromCell(row, endTimeIndex);
            if (billDateFrom == null) {
                errMsgs.add("第" + rIndex + "行：请确认 账单周期（止） 非空，单元格设置为日期格式");
            }
            LocalDate shouldPayDate = ExcelUtil.getLocalDateFromCell(row, payTimeIndex);
            if (billDateFrom == null) {
                errMsgs.add("第" + rIndex + "行：请确认 应付款日 非空，单元格设置为日期格式");
            }

            List<String> tempItem = new ArrayList<>();
            for (int i = headerArr.length, len = headerArr.length + billitems.size(); i < len; i++) {
                Cell itemCell = row.getCell(i);
                if (itemCell == null) {
                    tempItem.add(null);
                } else {
                    itemCell.setCellType(CellType.STRING);
                    tempItem.add(itemCell.getStringCellValue());
                }
            }

            List<List<String>> gropItems = new ArrayList<>();
            for (int i = 0; i < tempItem.size(); i++) {
                String feeId = tempItem.get(i);
                if (StringUtil.isBlank(feeId)) {
                    errMsgs.add("第" + rIndex + "行：费用id不能为空");
                }
                ChargeFeeType feeType = feeTypeMap.get(feeId);
                if (Objects.isNull(feeType)) {
                    continue;
                } else {
                    BigDecimal subTotal;

                    List<String> items = new ArrayList<>();
                    items.add(tempItem.get(i));
                    items.add(tempItem.get(i + 1));
                    items.add(tempItem.get(i + 2));
                    String feeName = tempItem.get(i + 1);
                    if (StringUtil.isBlank(feeName)) {
                        errMsgs.add("第" + rIndex + "行：费用名称不能为空");
                    }
                    if (FeesType.INSTRUMENT == feeType.getFeetype()) {

                        BigDecimal lastReadNumber = ExcelUtil.getBigDecimalFromCell(tempItem.get(i + 2));
                        if (lastReadNumber != null && CalculatorUtils.getDecimalPlace(lastReadNumber) > 1) {
                            errMsgs.add("第" + rIndex + "行：" + feeName + "请确认上次读数 小数位最多支持1位");
                        }
                        if (lastReadNumber != null && lastReadNumber.compareTo(BigDecimal.ZERO) < 0) {
                            errMsgs.add("第" + rIndex + "行：" + feeName + "请确认上次读数 不可为负数");
                        }
                        BigDecimal theReadNumber = ExcelUtil.getBigDecimalFromCell(tempItem.get(i + 3));
                        if (theReadNumber != null && CalculatorUtils.getDecimalPlace(theReadNumber) > 1) {
                            errMsgs.add("第" + rIndex + "行：" + feeName + "请确认本次读数 小数位最多支持1位");
                        }
                        if (theReadNumber != null && theReadNumber.compareTo(BigDecimal.ZERO) < 0) {
                            errMsgs.add("第" + rIndex + "行：" + feeName + "请确认本次读数 不可为负数");
                        }
                        BigDecimal amount = ExcelUtil.getBigDecimalFromCell(tempItem.get(i + 4));
                        if (amount != null && CalculatorUtils.getDecimalPlace(amount) > 2) {
                            errMsgs.add("第" + rIndex + "行：" + feeName + "请确认数量 小数位最多支持2位");
                        }
                        if (amount != null && amount.compareTo(BigDecimal.ZERO) < 0) {
                            errMsgs.add("第" + rIndex + "行：" + feeName + "请确认数量 不可为负数");
                        }
                        BigDecimal unitPrice = ExcelUtil.getBigDecimalFromCell(tempItem.get(i + 5));
                        if (unitPrice != null && CalculatorUtils.getDecimalPlace(unitPrice) > 2) {
                            errMsgs.add("第" + rIndex + "行：" + feeName + "请确认单价 小数位最多支持2位");
                        }
                        if (unitPrice != null && unitPrice.compareTo(BigDecimal.ZERO) < 0) {
                            errMsgs.add("第" + rIndex + "行：" + feeName + "请确认单价 不可为负数");
                        }

                        subTotal = ExcelUtil.getBigDecimalFromCell(tempItem.get(i + 6));

                        items.add(tempItem.get(i + 3));
                        items.add(tempItem.get(i + 4));
                        items.add(tempItem.get(i + 5));
                        items.add(tempItem.get(i + 6));
                        i += 6;
                    } else {
                        subTotal = ExcelUtil.getBigDecimalFromCell(tempItem.get(i + 2));
                        i += 2;
                    }
                    if (subTotal == null) {
                        errMsgs.add("第" + rIndex + "行：" + feeName + "请确认金额 不能为空，小数位最多2位");
                    }else{
                        if (CalculatorUtils.getDecimalPlace(subTotal) > 2) {
                            errMsgs.add("第" + rIndex + "行：" + feeName + "请确认金额 小数位最多支持2位");
                        }
                        if (subTotal.compareTo(BigDecimal.ZERO) < 0) {
                            errMsgs.add("第" + rIndex + "行：" + feeName + "请确认金额 不可为负数");
                        }
                    }
                    gropItems.add(items);
                }
            }

            // 数据读取结果
            if (!errMsgs.isEmpty()) {
                return Result.error(0, StringUtil.join(errMsgs, ";"));
            }

            JSONObject bill = new JSONObject();
            bill.put("houseNo", houseNo);
            bill.put("billDateFrom", billDateFrom);
            bill.put("billDateTo", billDateTo);
            bill.put("shouldPayDate", shouldPayDate);

            // 计算总金额
            BigDecimal total = totalMap.get(houseNo);
            List<JSONObject> billItems = billItemMap.get(houseNo);
            for (List<String> item : gropItems) {
                String feeId = item.get(0);
                String feeName = item.get(1);
                ChargeFeeType feeType = feeTypeMap.get(feeId);

                BigDecimal subTotal;
                JSONObject billItem = new JSONObject();
                if (FeesType.INSTRUMENT == feeType.getFeetype()) {
                    BigDecimal lastReadNumber = ExcelUtil.getBigDecimalFromCell(item.get(2));
                    BigDecimal theReadNumber = ExcelUtil.getBigDecimalFromCell(item.get(3));
                    BigDecimal amount = ExcelUtil.getBigDecimalFromCell(item.get(4));
                    BigDecimal unitPrice = ExcelUtil.getBigDecimalFromCell(item.get(5));
                    subTotal = ExcelUtil.getBigDecimalFromCell(item.get(6));
                    billItem.put("lastReadNumber", lastReadNumber);
                    billItem.put("theReadNumber", theReadNumber);
                    billItem.put("amount", amount);
                    billItem.put("unitPrice", unitPrice);
                }else{
                    subTotal = ExcelUtil.getBigDecimalFromCell(item.get(2));
                }
                billItem.put("feeId", feeId);
                billItem.put("feeName", feeName);
                billItem.put("subTotal", subTotal);

                if(null == billItems){
                    billItems = new ArrayList<>();
                    billItems.add(billItem);
                    billItemMap.put(houseNo, billItems);
                }else{
                    billItems.add(billItem);
                    billItemMap.put(houseNo, billItems);
                }

                if(null != subTotal){
                    if(null == total){
                        total = BigDecimal.ZERO.add(subTotal);
                    }else{
                        total = total.add(subTotal);
                    }
                    totalMap.put(houseNo, total);
                }
            }

            // 房屋账单信息分组
            JSONObject billInfo = billMap.get(houseNo);
            if(null == billInfo){
                billMap.put(houseNo, bill);
            }
        }

        for (String key:  billMap.keySet()) {
            JSONObject value = billMap.get(key);

            JSONObject house = basicClient.getHouseById(token, key);
            if (Objects.isNull(house)) {
                errMsgs.add("房屋id：" + key + "不存在");
            }else{
                value.put("house", house);
                String communityNo = house.getString("communityNo");
                JSONObject community = basicClient.getCommunityById(token, communityNo);
                if(Objects.nonNull(community)){
                    value.put("communityName", community.getString("name"));
                }
            }
        }

        // 读库取值结果
        if (!errMsgs.isEmpty()) {
            return Result.error(0, StringUtil.join(errMsgs, ";"));
        }

        // 写库环节
        for (String key : billMap.keySet()) {

            JSONObject value = billMap.get(key);

            ChargeBill bill = Pool.newInstance(ChargeBill.class);

            JSONObject house = value.getJSONObject("house");
            bill.setCommunityName(value.getString("communityName"));
            bill.setCommunityNo(house.getString("communityNo"));

            bill.setBuildingNo(house.getString("buildNo"));
            bill.setBuildingName(house.getString("buildName"));
            bill.setUnitName(house.getString("unitName"));
            bill.setFloorName(house.getString("floorName"));
            bill.setHouseName(house.getString("floorName") + house.getString("name"));
            bill.setHouseNo(house.getString("id"));

            bill.setBillDateFrom((LocalDate) value.get("billDateFrom"));
            bill.setBillDateTo((LocalDate) value.get("billDateTo"));
            bill.setShouldPayDate((LocalDate) value.get("shouldPayDate"));

            List<JSONObject> billItems = billItemMap.get(value.getString("houseNo"));
            billItems.forEach(js -> {
                ChargeBillItem billItem = Pool.newInstance(ChargeBillItem.class);
                billItem.setBillNo(bill.getId());
                billItem.setFeeId(js.getString("feeId"));
                billItem.setName(js.getString("feeName"));
                billItem.setSubTotal(js.getBigDecimal("subTotal"));

                ChargeFeeType feeType = feeTypeMap.get(billItem.getFeeId());
                if (FeesType.INSTRUMENT == feeType.getFeetype()) {
                    billItem.setLastReadNumber(js.getBigDecimal("lastReadNumber"));
                    billItem.setTheReadNumber(js.getBigDecimal("theReadNumber"));
                    billItem.setAmount(js.getBigDecimal("amount"));
                    billItem.setUnitPrice(js.getBigDecimal("unitPrice"));
                }
                jdbcSession.insert(billItem);
            });

            bill.setTotal(totalMap.get(value.getString("houseNo")));
            if (req.getBoolean("enable")) {
                bill.setEnableState(BillEnableState.ENABLE);
            } else {
                bill.setEnableState(BillEnableState.UNUSED);
            }

            setDepartmentNo(bill, token.getCompanyNo());

            jdbcSession.insert(bill);

            chargeLogService.saveLog(bill.getId(), BillOperateType.CREATE, null, token.getUsername(), token.getUuid());
        }

        StringBuilder successMsg = new StringBuilder();
        successMsg.append("新增");
        successMsg.append(billMap.size());
        successMsg.append("条缴费任务");
        if (req.getBoolean("enable")) {
            successMsg.append("（已启用）");
        } else {
            successMsg.append("（未启用）");
        }

        return Result.success(successMsg);
    }

    /**
     * 上传附件，账单置为已支付
     */
    @HasPermission("charge:bill:edit")
    @ReqMapping("/annex/save")
    public Object saveAnnex(Token token, JSONObject req) {

        JSONArray js = req.getJSONArray("fileIds");
        List<String> fileIds = js.stream()
                .map(String::valueOf)
                .filter(StringUtil::isNotBlank)
                .collect(Collectors.toList());
        if (fileIds.isEmpty()) {
            return Result.error(0, "上传凭证不能为空");
        }

        ChargeBill bill = jdbcSession.findOne(ChargeBill.class)
                .eq(req, "id")
                .exe();
        // 校验
        if (bill == null) {
            return Result.error(0, "账单不存在");
        }

        if (bill.getPaidState() != BillPaidState.UNPAID) {
            return Result.error(0, "当前状态不可操作");
        }
        if (bill.getEnableState() != BillEnableState.ENABLE) {
            return Result.error(0, "选择了未启用的账单");
        }

        // 执行
        chargeBillAnnexService.doInsertBillAnnex(bill.getId(), fileIds, ChargeAnnexType.PAYMENT_VOUCHER);

        bill.setPaidState(BillPaidState.PAID);
        bill.setPaidAt(LocalDateTime.now());
        bill.setPayMethod(BillPayMethod.MANUAL);
        jdbcSession.updateById(bill);

        try {
            //生成票据
            receiptOrderServiceImpl.savePdf(bill, token.getCompanyNo());
        }catch (Exception e){
            logger.error("账单：{},生成收据PDF文件失败！",bill.getId(),e);
        }

        chargeLogService.saveLog(bill.getId(), BillOperateType.MANUAL_UPDATE_PAID_STATE, req, token.getUsername(), token.getUuid());

        return Result.success();
    }

    /**
     * 上传附件，批量账单置为已支付
     */
    @HasPermission("charge:bill:edit")
    @ReqMapping("/batch/annex/save")
    public Object batchSaveAnnex(Token token, JSONObject req) {
        return chargeBillService.batchSaveAnnex(req, token);
    }

    /**
     * 设置催缴方式
     */
    @HasPermission("charge:bill:edit")
    @ReqMapping("/call/update")
    public Object updateCallConfig(Token token, JSONObject req) throws Exception{
        // 入参
        BillCallMethod callMethod = Enums.getEnum(BillCallMethod.class, req.getString("callMethod"));
        LocalDateTime configCallAt;
        if (BillCallMethod.TIMING == callMethod) {
            configCallAt = Converter.toLocalDateTime(req.get("configCallAt"));
            if (configCallAt.isBefore(LocalDateTime.now())) {
                return Result.error(0, "定时催缴时间不可早于当前");
            }
        } else if (BillCallMethod.IMMEDIATELY == callMethod) {
            configCallAt = LocalDateTime.now();
        } else {
            return Result.error(0, "未定义的催缴方式");
        }

        ChargeBill bill = jdbcSession.findOne(ChargeBill.class)
                .eq(req, "id")
                .exe();

        if (bill == null) {
            return Result.error(0, "不存在的账单");
        }

        if (bill.getPaidState() != BillPaidState.UNPAID) {
            return Result.error(0, "仅待缴费状态才可修改");
        }

        RemarkBuilder rb = new RemarkBuilder();

        rb.append("催缴方式", bill.getCallMethod(), callMethod);
        bill.setCallMethod(callMethod);

        rb.append("催缴时间", bill.getConfigCallAt(), configCallAt);
        bill.setConfigCallAt(configCallAt);

        bill.setCallState(BillCallState.UNDO);
        jdbcSession.updateById(bill);

        ChargeCallTask callTask = chargeCallService.findOneUnRunCallTaskByBill(bill);
        callTask.setCallMethod(callMethod);
        callTask.setConfigCallAt(configCallAt);
        callTask.setOperatorName(token.getUsername());
        jdbcSession.updateById(callTask);

        chargeLogService.saveLog(bill.getId(), BillOperateType.UPDATE_CALL_CONFIG, rb.build(), req, token.getUsername(), token.getUuid());

        chargeCallService.afterCallTaskUpdate(callTask,token.getCompanyNo());

        return Result.success();
    }

    /**
     * 单个编辑启用、关闭状态
     */
    @HasPermission("charge:bill:edit")
    @ReqMapping("/enableState/update")
    public Object updateEnableStateById(Token token, JSONObject req) {
        ChargeBill bill = jdbcSession.findOne(ChargeBill.class)
                .eq(req, "id")
                .exe();

        if (req.getBoolean("enable")) {
            // 启用
            if (bill.getEnableState() == BillEnableState.ENABLE) {
                return Result.success();
            }
            bill.setEnableState(BillEnableState.ENABLE);
            jdbcSession.updateById(bill);

            chargeLogService.saveLog(bill.getId(), BillOperateType.UPDATE_ENABLE_ON, req, token.getUsername(), token.getUuid());
        } else {
            // 关闭
            if (bill.getEnableState() == BillEnableState.UNUSED) {
                return Result.success();
            }

            if (bill.getPaidState() == BillPaidState.PAID) {
                return Result.error(0, "已缴费账单不可关闭");
            }
            bill.setEnableState(BillEnableState.UNUSED);
            jdbcSession.updateById(bill);

            chargeLogService.saveLog(bill.getId(), BillOperateType.UPDATE_ENABLE_OFF, req, token.getUsername(), token.getUuid());
        }

        return Result.success();
    }

    /**
     * 批量启用
     */
    @HasPermission("charge:bill:edit")
    @ReqMapping("/batch/enableState/on")
    public Object batchUpdateEnableStateOn(Token token, JSONObject req) {
        List<ChargeBill> bills = jdbcSession.findArray(ChargeBill.class)
                .in("id", req.getJSONArray("ids"))
                .exe();

        for (ChargeBill bill : bills) {
            if (bill.getEnableState() == BillEnableState.ENABLE) {
                continue;
            }
            bill.setEnableState(BillEnableState.ENABLE);
            jdbcSession.updateById(bill);

            chargeLogService.saveLog(bill.getId(), BillOperateType.UPDATE_ENABLE_ON, req, token.getUsername(), token.getUuid());
        }
        return Result.success();
    }

    /**
     * 批量关闭
     */
    @HasPermission("charge:bill:edit")
    @ReqMapping("/batch/enableState/off")
    public Object batchEnableStateOff(Token token, JSONObject req) {
        List<ChargeBill> bills = jdbcSession.findArray(ChargeBill.class)
                .in("id", req.getJSONArray("ids"))
                .exe();

        // 校验
        for (ChargeBill bill : bills) {
            if (bill.getPaidState() == BillPaidState.PAID) {
                return Result.error(0, "已缴费账单不可关闭");
            }
        }

        // 执行
        for (ChargeBill bill : bills) {
            if (bill.getEnableState() == BillEnableState.UNUSED) {
                continue;
            }
            bill.setEnableState(BillEnableState.UNUSED);
            jdbcSession.updateById(bill);

            chargeLogService.saveLog(bill.getId(), BillOperateType.UPDATE_ENABLE_OFF, req, token.getUsername(), token.getUuid());
        }
        return Result.success();
    }

    /**
     * 批量设置催缴方式
     */
    @HasPermission("charge:bill:edit")
    @ReqMapping("/batch/call/update")
    public Object batchUpdateCallConfig(Token token, JSONObject req) throws Exception{
        // 入参
        BillCallMethod callMethod = Enums.getEnum(BillCallMethod.class, req.getString("callMethod"));
        LocalDateTime configCallAt;
        if (BillCallMethod.TIMING == callMethod) {
            configCallAt = Converter.toLocalDateTime(req.get("configCallAt"));
            if (configCallAt.isBefore(LocalDateTime.now())) {
                return Result.error(0, "定时催缴时间不可早于当前");
            }
        } else if (BillCallMethod.IMMEDIATELY == callMethod) {
            configCallAt = LocalDateTime.now();
        } else {
            return Result.error(0, "未定义的催缴方式");
        }

        List<ChargeBill> bills = jdbcSession.findArray(ChargeBill.class)
                .in("id", req.getJSONArray("ids"))
                .exe();

        // 校验
        for (ChargeBill bill : bills) {
            if (bill.getPaidState() != BillPaidState.UNPAID) {
                return Result.error(0, "仅待缴费状态才可修改");
            }
        }

        for (ChargeBill bill : bills) {

            RemarkBuilder rb = new RemarkBuilder();

            rb.append("催缴方式", bill.getCallMethod(), callMethod);
            bill.setCallMethod(callMethod);

            rb.append("催缴时间", bill.getConfigCallAt(), configCallAt);
            bill.setConfigCallAt(configCallAt);

            bill.setCallState(BillCallState.UNDO);

            jdbcSession.updateById(bill);

            ChargeCallTask callTask = chargeCallService.findOneUnRunCallTaskByBill(bill);
            callTask.setCallMethod(callMethod);
            callTask.setConfigCallAt(configCallAt);
            callTask.setOperatorName(token.getUsername());
            jdbcSession.updateById(callTask);

            chargeLogService.saveLog(bill.getId(), BillOperateType.UPDATE_CALL_CONFIG, rb.build(), req, token.getUsername(), token.getUuid());

            chargeCallService.afterCallTaskUpdate(callTask,token.getCompanyNo());
        }

        return Result.success();
    }

    /**
     * 按页面条件批量导出
     */
    @HasPermission("charge:bill:view")
    @ReqMapping("/export")
    public Object download(JSONObject req) throws IOException {

        FindArray<ChargeBill> find = jdbcSession.findArray(ChargeBill.class)
                .eq(req, "enableState", "paidState")
                .like(req, "communityName", "buildingName")

                .eq("deleted", false)
                .order("communityNo", "DESC")
                .order("buildingName", "ASC")
                .order("unitName", "ASC")
                .order("floorName", "ASC")
                .order("houseName", "ASC")
                .order("billDateFrom", "ASC")
                .order("shouldPayDate", "ASC")
                .order("id", "ASC");

        if (StringUtil.isNotBlank(req.getString("houseName"))) {
            find.markBegin()
                    .like("unitName", req.get("houseName"))
                    .or()
                    .like("floorName", req.get("houseName"))
                    .or()
                    .like("houseName", req.get("houseName"))
                    .markEnd();
        }
        if (StringUtil.isNotBlank(req.getString("shouldPayDateFrom"))) {
            find.maxEq("shouldPayDate", req.get("shouldPayDateFrom"));
        }
        if (StringUtil.isNotBlank(req.getString("shouldPayDateTo"))) {
            find.minEq("shouldPayDate", req.get("shouldPayDateTo"));
        }
        if (StringUtil.isNotBlank(req.getString("billDateFrom"))) {
            find.maxEq("billDateFrom", req.get("billDateFrom"));
        }
        if (StringUtil.isNotBlank(req.getString("billDateTo"))) {
            find.minEq("billDateTo", req.get("billDateTo"));
        }

        List<ChargeBill> bills = find.exe();

        if (bills.isEmpty()) {
            return Result.error(0, "无记录导出");
        }

        XSSFWorkbook wb = chargeBillService.generateWorkBook(bills);

        File tmpFile = ExcelUtil.writeXlsFile(wb, "缴费任务", ".xlsx");
        return Result.download(tmpFile);
    }

    /**
     * 操作日志分页列表
     */
    @HasPermission("charge:bill:view")
    @ReqMapping("/log/page")
    public Object pageLog(JSONObject req) {
        ChargeBill bill = jdbcSession.findOne(ChargeBill.class)
                .eq(req, "id")
                .exe();

        if (bill == null) {
            return Result.error(0, "不存在的账单");
        }

        Page<ChargeBillLog> page = jdbcSession.findPage(ChargeBillLog.class)
                .eq("billNo", bill.getId())
                .order("createTime", "DESC")
                .page(req)
                .exe();

        return Result.success(page);
    }

    /**
     * 校验是否有重合的缴费账单周期
     */
    @HasPermission("charge:bill:view")
    @ReqMapping("/validate/bill_date")
    public Object validateBillDate(String id) {
        ChargeBill bill = Pool.get(ChargeBill.class, id);
        if (bill == null) {
            return Result.error(0, "不存在的缴费任务");
        }
        if (chargeBillService.existDuplicateBillDateRangeBill(bill)) {
            return Result.error(0, "存在时间重叠的账单");
        }
        return Result.success();
    }

    public static  <T> List<List<T>> fixedGrouping(List<T> source, int n) {
        if (null == source || source.size() == 0 || n <= 0){
            return null;
        }

        List<List<T>> result = new ArrayList<>();
        int sourceSize = source.size();
        int size = (source.size() / n);
        for (int i = 0; i < size; i++) {
            List<T> subset = new ArrayList<T>();
            for (int j = i * n; j < (i + 1) * n; j++) {
                if (j < sourceSize) {
                    subset.add(source.get(j));
                }
            }
            result.add(subset);
        }
        return result;
    }

    /**
     * 说明：判断是否为BigDecimal
     * @param integer
     * @return
     */
    public static boolean isBigDecimal(String integer) {
        try{
            new BigDecimal(integer);
            return false;
        }catch(NumberFormatException e)
        {
            return true;
        }
    }


    /**
     *修复历史账单单元为空的数据
     */
    @ReqMapping("/syncChargeHistoryData")
    public Object syncChargeHistoryData(Token token) throws Exception {
        List<ChargeBill> chargeList = jdbcSession.findArray(ChargeBill.class)
                .eq("deleted", false)
                .exe();
        if(Objects.nonEmpty(chargeList)){
            List<ChargeBill> filterChargeList = filterChargeList(chargeList);
            if(Objects.nonEmpty(filterChargeList)){
                for (ChargeBill c : filterChargeList){
                    JSONObject house = basicClient.getHouseById(token, c.getHouseNo());
                    if (Objects.isNull(house)) {
                        return Result.error(0, "房屋不存在");
                    }
                    c.setUnitName(house.getString("unitName"));
                }
                jdbcSession.updateById(filterChargeList);
            }
        }
        return Result.success();
    }

    private List<ChargeBill> filterChargeList(List<ChargeBill> chargeList){
        List<ChargeBill>  fixedUnitChargeList = new ArrayList<ChargeBill>();
        for (ChargeBill cb : chargeList){
            if(Objects.isEmpty(cb.getUnitName())){
                fixedUnitChargeList.add(cb);
            }
        }
        return  fixedUnitChargeList ;
    }

    /**
     * 手动生成收据PDF
     * @param entity
     * @param token
     * @return
     * @throws Exception
     */
    @Bean(newInstance=true, copy=true)
    @HasPermission("charge:bill:edit")
    @ReqMapping("/manualSave")
    public Object manualSave(ChargeBill entity, Token token) throws Exception {

        return receiptOrderServiceImpl.savePdf(entity,token.getCompanyNo());
    }

    @Bean(newInstance=true, copy=true)
    @HasPermission("charge:bill:edit")
    @ReqMapping("/manualPaySave")
    public Object manualPaySave(ChargeBill entity, Token token) throws Exception {

        return payManageService.generatePdf(entity,"");
    }


    private void setDepartmentNo(ChargeBill entity, String companyNo) throws Exception {
        JSONObject community = basicClient.getCommunityById(companyNo,
                entity.getCommunityNo());
        if(Objects.nonEmpty(community)){
            String parent_id = community.getString("parent_id");
            if(Objects.nonEmpty(parent_id)){
                entity.setDepartmentNo(parent_id);
            }
            JSONArray parent_ids = community.getJSONArray("parent_ids");
            if(Objects.nonEmpty(parent_ids)){
                List<String> departmentNos = JSONObject.parseArray(parent_ids.toJSONString(), String.class);
                entity.setDepartmentNos(departmentNos);
            }
        }
    }
}
